﻿using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Duellum.Core;
using JpLabs.Extensions;

namespace Duellum.Map
{
	public static class DuelMapEncoder
	{
		public const string VERSION = "0.1";
		
		static public void SaveMapTo(DuelMap map, TextWriter writer)
		{
			var xshape = new XElement(
				"shape",
				new XElement(
					"box",
					new XAttribute("lower", map.Box.Lower.ToString()),
					new XAttribute("upper", map.Box.Upper.ToString())
				)
			);
			
			/// TODO: Current format uses a byte stream. So, the limit for position and object type index is 256.
			/// I need a variable-lenght representation for this stuff. Sounds like Huffman
			
			IDuelMapEntry[] entries = map.QueryAll().ToArray();
			
			//var distinct = entries.Select(e => e.Object).Distinct().OrderBy(o => o.ToString()).ToArray();
			var distinct
				= entries
				.Select(e => e.Object.Id)
				.GroupBy(id => id)
				.Select(g => new { Id = g.Key, Count = g.Count() })
				.OrderByDescending(a => a.Count)
				.ThenBy(a => a.Id);
			
			var dict
				= distinct
				.Select((a, i) => new { Id = a.Id, Index = i })
				.ToDictionary(a => a.Id, a => a.Index);

			var xtable = new XElement(
				"table",
				dict.Select(p => new XElement(
					"type",
					new XAttribute("id", p.Key),
					new XAttribute("ix", p.Value)
					
					//DEBUG ONLY (or not)
					,new XAttribute("count", distinct.First(a => a.Id == p.Key).Count)
				))
			);
			
			var content
				= entries
				.OrderBy(e => e.Position.GetHashCode())
				.ThenBy(e => e.Object.Id)
				.Select(e => new { Position = e.Position, ObjId = dict[e.Object.Id] })
				.SelectMany(a => a.Position.Concat(a.ObjId))
				.Select(i => (byte)i)
				
				//DEBUG ONLY
				//.Select(a => a.ObjId.ToString() + "-" + string.Join(",", a.Position.Select(b => b.ToString()).ToArray()) )
				
				.ToArray();
			
			
			var xcontent = new XElement(
				"content",
				Convert.ToBase64String(content, Base64FormattingOptions.InsertLineBreaks) //new XCData()
				
				//DEBUG ONLY
				//new XCData(
				//    content.Aggregate(
				//        new StringBuilder(),
				//        ( (sb, b) => sb.Append(b).AppendLine() ),
				//        ( sb => sb.ToString() )
				//    )
				//)
			);
			
			
			//Map Attributes
			map.Attributes[DuelMapAttributes.FormatVersion] = VERSION;
			var xattributesChildren = map.Attributes.Select( pair => new XElement(pair.Key, pair.Value) );
			
			////Add map version attribute, if it doesn't exists
			//if (!map.Attributes.ContainsKey(DuelMapAttributes.FormatVersion))
			//{
			//    xattributesChildren
			//        = xattributesChildren
			//        .Concat( new XElement(DuelMapAttributes.FormatVersion, VERSION) );
			//}
			
			var xattributes = new XElement("attributes", xattributesChildren);
			
			var xroot = new XElement("duelmap", xattributes, xshape, xtable, xcontent);
			
			var xdoc = new XDocument(xroot);
			
			xdoc.Save(writer);
			//StreamWriter
			//StringWriter
		}
		
		//private static string ToBase64(int index)
		//{
		//    return Convert.ToBase64String(new []{
		//        (byte)(index >> 24 & 0xFF),
		//        (byte)(index >> 16 & 0xFF),
		//        (byte)(index >> 8  & 0xFF),
		//        (byte)(index       & 0xFF),
		//    });
		//}
	}
}
